package com.jamal2362.apktool.util;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import brut.util.OS;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import pxb.android.axml.AxmlReader;
import pxb.android.axml.AxmlVisitor;
import pxb.android.axml.AxmlWriter;
import pxb.android.axml.NodeVisitor;
import com.jamal2362.apktool.R;
import com.jamal2362.apktool.fragment.files.Refreshable;
import com.jamal2362.apktool.task.SignUtil;

public class QuickEdit extends AsyncTask<String, String, Boolean>
{
	private final String[] ignoreStarts = new String[]{"assets/ugc", "assets/yandexnavi/fonts/tiles", "res/raw/netdisk", "assets/yandexnavi/fonts/", "res/raw/langid.data", "res/raw/joda", "assets/adp", "assets/js-modules/UNBUNDLE", "res/raw/estool", "res/raw/feature", "res/raw/irlocaldata", "assets/sound-strings/", "res/raw/selection", "res/raw/sb", "res/raw/guides", "res/raw/metadata.json", "res/raw/sm", "assets/cuisine-strings/", "res/raw/fill", "res/raw/transform", "assets/metadata.json", "res/raw/copic", "res/raw/layers", "res/raw/dav", "res/raw/test", "res/raw/timelapse", "res/raw/pulsar", "res/raw/cuscs", "res/raw/gtm", "res/raw/megviifacepp", "assets/countries-strings/", "assets/services/", "res/raw/ep", "assets/ABBYY.license", "res/raw/bnbp", "libs/", "res/raw/tw", "res/raw/bear.tiff", "res/raw/yvideo", "res/raw/spki"};
	private final String[] ignoreEnds = new String[]{".jpg", ".jpeg", ".png", ".gif", ".wav", ".mp2", ".mp3", ".ogg", ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".avc", ".der", ".pfx", ".kml", ".pic", ".bc", ".key", ".glsl", ".plugin", ".p12", ".dat", ".cer", ".pb", ".bks", ".woff2", ".res", "/thumbnail", ".binarypb", ".bin", ".dict", ".zip", ".pk8", ".mov", ".crt"};
	private final String[] components = {"activity", "activity-alias", "provider", "receiver", "service"};
	private String originalPname;
	private String label;
	private String pname;
	private String version;
	private int code;
	private int minSdk;
	private int targetSdk;
	private boolean removeSplit;
	private Context context;
	private Refreshable refresh;
	private AlertDialog progress;
	private Bitmap bitmap;
	private String iconName;
	private boolean redirect;
	private String error;

	public QuickEdit(Context ctx, Refreshable refresh)
	{
		this.context = ctx;
		this.refresh = refresh;
		this.code = 0;
		this.minSdk = 0;
		this.targetSdk = 0;
		this.redirect = false;
		this.removeSplit = false;
	}

	@Override
	protected void onPreExecute()
	{
		LayoutInflater inflater = LayoutInflater.from(context);
		View view = inflater.inflate(R.layout.search_progress, null);
		TextView tview = view.findViewById(R.id.searchprogressTextView1);
		tview.setText(R.string.mplease_wait);
		progress = new AlertDialog.Builder(context)
			.setView(view)
			.setCancelable(false)
			.create();
		progress.show();
	}

	@Override
	protected Boolean doInBackground(final String[] p1)
	{
		final File s = new File(p1[0]);
		final File f = new File(p1[1]);
		try
		{
			String type = null;
			String name = null;
			if (bitmap != null && iconName != null)
			{
				String[] tn = iconName.split("/");
				type = tn[0];
				name = tn[1];
				IconGenerate.generate(f.getParent(), bitmap, name);
			}
			repackApk(p1[0], p1[1], type, name);
			final File aligned = new File(f.getParentFile(), "_" + f.getName());
			List<String> cmd = new ArrayList<String>();
			cmd.add(Settings.zipAlign);
			cmd.add("-f");
			cmd.add("4");
			cmd.add(f.getAbsolutePath());
			cmd.add(aligned.getAbsolutePath());
			exec(cmd);
			SignUtil.loadKey(context, new SignUtil.LoadKeyCallback(){
					@Override
					public void call(SignUtil signTool)
					{
						File out = getOutFile(f, f.getName());
						signTool.sign(aligned, out, minSdk);
					}
				});
			redirect = !s.getParentFile().canWrite();
			return true;
		}
		catch (Exception e)
		{
			error = Log.getStackTraceString(e);
			e.printStackTrace();
			return false;
		}
		finally
		{
			OS.rmdir(f.getParentFile());
		}
	}

	@Override
	protected void onPostExecute(Boolean result)
	{
		progress.cancel();
		if (result)
		{
			if (redirect)
			{
				Runnable go = new Runnable()
				{
					@Override
					public void run()
					{
						refresh.refresh(new File(Settings.output_directory));
					}
				};
				MUtil.createDialog(context, context.getString(R.string.complete) + "!", R.string.go_to, go);
			}
			else
			{
				refresh.refresh();
			}
		}
		else
		{
			Runnable copy = new Runnable()
			{
				@Override
				public void run()
				{
					TextUtil.setClipboard(context, error);
				}
			};
			MUtil.createDialog(context, context.getString(R.string.error) + ": " + error, R.string.copy, copy);
		}
		super.onPostExecute(result);
	}

	public void setIconName(String str)
	{
		if (!str.equals(""))
			this.iconName = str;
	}

	public void setLabel(String str)
	{
		this.label = str;
	}

	public void setPname(String str)
	{
		this.pname = str;
	}

	public void setOriginalPname(String str)
	{
		this.originalPname = str;
	}

	public void setVersion(String str)
	{
		this.version = str;
	}

	public void setCode(int code)
	{
		this.code = code;
	}

	public void setMinSdk(int code)
	{
		this.minSdk = code;
	}

	public void setTargetSdk(int code)
	{
		this.targetSdk = code;
	}

	public void removeSplit(boolean rm)
	{
		this.removeSplit = rm;
	}

	public void setBitmap(Bitmap bit)
	{
		this.bitmap = bit;
	}

    private void repackApk(String srcApk, String targetApk, String type, String name) throws IOException
	{
		ZipInputStream zin = new ZipInputStream(new FileInputStream(srcApk));
		ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(targetApk));
		zout.setLevel(Deflater.BEST_COMPRESSION);
		ZipEntry entry;
		byte[] buffer = new byte[2048];
		File dir = new File(targetApk).getParentFile();
		while ((entry = zin.getNextEntry()) != null)
		{
			String ename = entry.getName();
			if (type != null && name != null && ename.startsWith("res/" + type) && ename.endsWith(name + ".png"))
			{
				int length;
				File ic = null;
				for (String dens : IconGenerate.mDens)
				{
					if (ename.startsWith("res/" + type + "-" + dens))
						ic = new File(dir.getAbsolutePath() + File.separator + dens + File.separator + name + ".png");
				}
				if (ic == null)
					ic = new File(dir.getAbsolutePath() + File.separator + "xxxhdpi" + File.separator + name + ".png");
				FileInputStream fin = new FileInputStream(ic);
				ZipEntry outEntry = new ZipEntry(ename);
				outEntry.setCompressedSize(ic.length());
				outEntry.setMethod(ZipEntry.DEFLATED);
				zout.putNextEntry(outEntry);
				while ((length = fin.read(buffer, 0, buffer.length)) > 0)
				{
					zout.write(buffer, 0, length);
				}
				zout.flush();
				zout.closeEntry();
			}
			else if ("AndroidManifest.xml".equals(ename))
			{
				File tempFile = new File(dir, "tmp_unchanged.xml");
				FileOutputStream fout = new FileOutputStream(tempFile);
				int length;
				while ((length = zin.read(buffer, 0, buffer.length)) > 0)
				{
					fout.write(buffer, 0, length);
				}
				fout.flush();
				fout.close();
				File tempChangedFile = new File(dir, "tmp_changed.xml");
				try
				{
					a(tempFile, tempChangedFile);
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
				FileInputStream fin = new FileInputStream(tempChangedFile);
				ZipEntry outEntry = new ZipEntry(ename);
				outEntry.setCompressedSize(tempChangedFile.length());
				outEntry.setMethod(ZipEntry.DEFLATED);
				zout.putNextEntry(outEntry);
				while ((length = fin.read(buffer, 0, buffer.length)) > 0)
				{
					zout.write(buffer, 0, length);
				}
				zout.flush();
				zout.closeEntry();
			}
			else
			{
				ZipEntry outEntry = new ZipEntry(ename);
				long size = entry.getSize();
				long crc = entry.getCrc();
				if (isIgnore(ename) && crc >= 0 && size >= 0)
				{
					outEntry.setMethod(ZipEntry.STORED);
					outEntry.setSize(size);
					outEntry.setCrc(crc);
				}
				else
				{
					outEntry.setMethod(ZipEntry.DEFLATED);
				}
				outEntry.setCompressedSize(entry.getSize());
				zout.putNextEntry(outEntry);
				int length;
				while ((length = zin.read(buffer, 0, buffer.length)) > 0)
				{
					zout.write(buffer, 0, length);
				}
				zout.flush();
				zout.closeEntry();
			}
			zin.closeEntry();
		}
		zin.close();
		zout.flush();
		zout.close();
	}

    private void a(File a, File b) throws Exception
	{
        InputStream is = new FileInputStream(a);
        byte[] xml = new byte[is.available()];
        is.read(xml);
        is.close();
        AxmlReader rd = new AxmlReader(xml);
        AxmlWriter wr = new AxmlWriter();
        rd.accept(new AxmlVisitor(wr) {
				public NodeVisitor child(String ns, String name)      //manifest  
				{
					return new NodeVisitor(super.child(ns, name))  
					{
						public NodeVisitor child(String ns, String name)  //manifest's child nodes  
						{  
							if (name.equalsIgnoreCase("uses-sdk"))
							{
								return new NodeVisitor(super.child(ns, name)) {  

									@Override
									public void attr(String ns, String name, int resourceId, int type, Object value)
									{
										if (name.equalsIgnoreCase("minSdkVersion") && minSdk > 0)
										{
											value = minSdk;
											type = TYPE_FIRST_INT;
										}
										else if (name.equalsIgnoreCase("targetSdkVersion") && targetSdk > 0)
										{
											value = targetSdk;
											type = TYPE_FIRST_INT;
										}
										super.attr(ns, name, resourceId, type, value);
									}
								};  
							}
							else if (name.equalsIgnoreCase("application"))
							{
								return new NodeVisitor(super.child(ns, name)) {  
									public NodeVisitor child(String ns, String name)
									{
										if (pname == null || originalPname == null)
										{
											return super.child(ns, name);
										}
										for (String component : components)
										{
											if (name.equalsIgnoreCase(component))
											{
												return new NodeVisitor(super.child(ns, name)) { 

													@Override
													public void attr(String ns, String name, int resourceId, int type, Object value)
													{
														if (name.equalsIgnoreCase("name") && value instanceof String)
														{
															int check = ((String)value).indexOf(".");
															if (check < 0)
															{
																value = originalPname + "." + value;
															}
															else if (check == 0)
															{
																value = originalPname + value;
															}
															type = TYPE_STRING;
														}
														super.attr(ns, name, resourceId, type, value);
													}
												};
											}
										}
										return super.child(ns, name);  
									}

									@Override
									public void attr(String ns, String name, int resourceId, int type, Object value)
									{
										if (name.equalsIgnoreCase("label") && label != null)
										{
											value = label;
											type = TYPE_STRING;
										}
										else if (name.equalsIgnoreCase("extractNativeLibs"))
										{
											return;
										}
										else if (name.equalsIgnoreCase("isSplitRequired") && removeSplit)
										{
											return;
										}
										super.attr(ns, name, resourceId, type, value);
									}  
								};  
							}
							return super.child(ns, name);
						}

						@Override
						public void attr(String ns, String name, int resourceId, int type, Object value)
						{
							if (name.equalsIgnoreCase("package") && pname != null)
							{
								value = pname;
								type = TYPE_STRING;
							}
							else if (name.equalsIgnoreCase("versionName") && version != null)
							{
								value = version;
								type = TYPE_STRING;
							}
							else if (name.equalsIgnoreCase("versionCode") && code > 0)
							{
								value = code;
								type = TYPE_FIRST_INT;
							}
							super.attr(ns, name, resourceId, type, value);
						}
					};  
				}  
			});
        byte[] modified = wr.toByteArray();
        FileOutputStream fos = new FileOutputStream(b);
        fos.write(modified);
        fos.close();
    }

	private File getOutFile(File f, String name)
	{
		String dir = f.getParentFile().getParent();
		if (name == null)
			name = f.getName();
		name = FileUtils.genNameApk(context, dir, name, "_mod", 0);
		return new File(dir, name);
	}

	private boolean isIgnore(String name)
	{
		if (name.toLowerCase().startsWith("r/"))
		{
			return true;
		}
		for (String start : ignoreStarts)
		{
			if (name.startsWith(start))
			{
				return true;
			}
		}
		if (name.startsWith("res/raw") || name.startsWith("assets/"))
		{
			for (String end : ignoreEnds)
			{
				if (name.endsWith(end))
				{
					return true;
				}
			}
		}
		return false;
	}

	private int exec(List<String> cmd)
	{
        Process ps = null;
        int exitValue = -99;
        try
		{
            ProcessBuilder builder = new ProcessBuilder(cmd);
            ps = builder.start();
            exitValue = ps.waitFor();
        }
		catch (Exception ex)
		{
			error = Log.getStackTraceString(ex);
        }
		return exitValue;
    }
}
